Een Ajax bibliotheek
Ajax
waarin we alle functionaliteit van het XMLHttpRequest
object onderbrengen.Bron
Matthew Eernisse, Build Your Own AJAX Web Applications, “Create responsive web applications with the power of AJAX!”. SitePoint Pty. Ltd., Australia, 2006.
Hoe?
In JavaScript declareren we geen klassen met een complexe syntaxis zoals we dat in C# of Java doen. We schrijven een constructor functie om een instantie van een 'klasse' te maken:
- schrijf een constructor functie, de naam van die functie is de naam van de klasse. Het is de gewoonte om de naam van een constructor functie in pascalnotatie te schrijven;
- voeg eigenschappen aan het te bouwen object toe met behulp van het this sleutelwoord gevolgd door een punt;
- voeg methoden toe aan het object zoals we eigenschappen toevoegen door gebruik te maken van de function constructor syntax in JavaScript;
De eigenschappen
We beginnen met de constructor voor onze Ajax
klasse. We voegen de eigenschappen en methoden toe die we nodig hebben om het XMLHttpRequest
object te kunnen werken:
// De Ajax klasseconstructor: function Ajax() { this.request = null; this.url = null; this.status = null; this.statusText = ''; this.method = 'GET'; this.asynchronousFlag = true; this.postData = null; this.readyState = null; this.responseText = null; this.responseXML = null; this.responseHandle = null; this.errorHandle = null; this.responseFormat = 'text', // 'text', 'xml', 'object' this.mimeType = null; this.accessToken = null; // access token te gebruiken met oAuth this.contentType = 'application/x-www-form-urlencoded'; // default content-type }
Methoden implementeren
We gaan nu enkele methoden voor de Ajax klasse implementeren. We hebben functies nodig om een het XMLHttpRequest object in te stellen en om requests te versturen.
Een XMLHttpRequest
object creëren
De initialize
methode maakt een XMLHttpRequest
object aan. Hoe je dit object kan aanmaken, verschilt van browser tot browser. Er bestaat een verschil tussen Microsoft Internet Explorer 6 en andere browsers als Opera, Safari, Mozilla, etc. Microsoft Internet Explorer 6 en vroegere versie maken gebruik van ActiveX object, waarbij we er rekening mee moeten houden dat dit uitgeschakeld kan worden. Onze initialize methode moet met elke mogelijkheid rekening houden en op elegante wijze degraderen (dit is lang geleden geschreven, nu is het misschien niet meer nodig om daar rekening mee te houden):
// Methoden: // Een XMLHttpRequest object maken this.initialize = function() { if (!this.request) { try { // Probeer een object te creëren voor Firefox, Safari, IE7 this.request = new XMLHttpRequest(); } catch (e) { try { // Probeer een object te creëren voor latere versies IE this.request = new ActiveXObject('MSXML2.XMLHTTP'); } catch (e) { try { // Probeer een object te maken met oudere versies van IE this.request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { // Kan geen XMLHttpRequest maken return false; } } } } return true; };
De request openen
De setupRequest
methode roept de methode initialize aan, die een XMLHttpRequest
object creëert met de naam this.request, en het vervolgens opent zodat de instellingen van de http request bepaald kunnen worden.
// Een request instellen this.setupRequest = function() { // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het // retourneer if (!this.initialize()) { alert('Het XMLHttpRequest object kan niet gecreëerd worden!'); return; } // Een XMLHttpRequest object is met succes gecreëerd this.request.open(this.method, this.url, this.asynchronousFlag); };
De open
methode heeft drie parameters:
- Method: De meest gebruikte methoden zijn GET en POST. Volgens de HTPP specificatie (RFC 2616) zijn de methoden van die requests hoofdlettergevoelig. Zorg ervoor dat je de methode altijd in hoofdletters meegeeft.
- URL: Die parameter geeft aan welke pagina opgevraagd (GET) of verstuurd (POST) wordt.
- Asynchronous Flag: Als die parameter is ingesteld op true, gaat je JavaScript verder worden uitgevoerd terwijl je wacht op het antwoord op je request. Is die parameter false, houdt je JavaScript code halt totdat de server het antwoord terug heeft gestuurd. Het is precies de mogelijkheid om asynchroon met de server te communiceren die het nut van het hele Ajax gebeuren uitmaakt. Die parameter moet altijd op true worden ingesteld.
In onze gloednieuwe Ajax
klasse worden de method
en asynchronousFlag
eigenschappen op redelijke standaard waarden ingesteld, namelijk GET en true. Maar de URL eigenschap moet je natuurlijk altijd zelf instellen.
De request header instellen voor geverifiëerde aanvragen
We gaan na of er een accesToken is ingesteld waarmee we een geverifiëerde request kunnen maken:
if (this.accessToken) { this.request.setRequestHeader('Authorization', 'Bearer ' + this.accessToken); }
Het Content-Type
Header veld
Het Content-Type
veld beschrijft de gegevens die in de body worden meegegeven zodat de ontvangende user agent de gepast methode kan uitkiezen om de gegevens aan de gebruiker te presenteren of om de gegevens te verwerken. De standaard waarde is application/x-www-form-urlencoded
. Mogelijke waarden vind je op
Content-Type
field.
if (this.method == 'POST') { this.request.setRequestHeader('Content-Type', this.contentType); }
De onreadystatechange Event Handler instellen
Nadat we een nieuw XMLHttpRequest
object hebben aangemaakt en geopend, moeten we nog aangeven wat er moet gebeuren wanneer er een respons ontvangen wordt. Daarvoor implementeren we de onreadystatechange
event handler van het XMLHttpRequest
object. Als een HTTP request op de server verwerkt wordt de voortgang aangegeven door wijzigingen in de readyState
eigenschap. Die eigenschap is een integer die één van de volgende toestanden weergeeft:
- 0: verbinding nog niet gemaakt, de methode open is nog niet aangeroepen
- 1: bezig met inladen, de methode send is nog niet ingeroepen
- 2: ingeladen, de send methode is uitgevoerd, maar nog geen antwoord ontvangen
- 3: interactief, het antwoord wordt gedownload, maar de responseText eigenschap bevat slechts gedeeltelijke informatie
- 4: volledig ingeladen, het antwoord is volledig gedownload en de request is volledig uitgevoerd.
Het XMLHttpRequest
object vuurt een event af telkens als de eigenschap readyState
verandert. In de afhandelaar voor dit event checken we de readyState eigenschap en als die de waarde 4 heeft (volledig uitgevoerd) handelen we het antwoord van de server af.
Een basisschets voor de eventafhandelaar voor het onreadystatechange
event in onze Ajax
klasse ziet er zo uit:
// Een request instellen this.setupRequest = function() { // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het // retourneer if (!this.initialize()) { alert('Het XMLHttpRequest object kan niet gecreëerd worden!'); return; } // Een XMLHttpRequest object is met succes gecreëerd this.request.open(this.method, this.url, this.asynchronousFlag); // Een functie toevoegen om het onreadystatechange event af te handelen var me = this; // fix loss of scope in inner function this.request.onreadystatechange = function() { // handel de event af als de request volledig uitgevoerd is if (me.request.readyState == 4) { // Verwerk het antwoord van de server } } };
Later vullen we de code in. Voor het moment volstaat het om te weten dat je die event handler moet instellen vooraleer de request verzonden wordt.
De request verzenden
Gebruik de send
methode van het XMLHttpRequest
object om de HTPP request op te starten:
// Een request instellen
this.setupRequest = function() { // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het // retourneer if (!this.initialize()) { alert('Het XMLHttpRequest object kan niet gecreëerd worden!'); return; } // Een XMLHttpRequest object is met succes gecreëerd this.request.open(this.method, this.url, this.asynchronousFlag); // Een functie toevoegen om het onreadystatechange event af te handelen var me = this; // fix loss of scope in inner function this.request.onreadystatechange = function() { // handel de event af als de request volledig uitgevoerd is if (me.request.readyState == 4) { // Verwerk het antwoord van de server } } }; // Verstuur de request met de send method van het XMLHttpRequest object this.request.send(this.postData);
De send
methode heeft één parameter die voor POST gegevens gebruikt wordt. Als de request een GET is die geen gegevens doorgeeft wordt die parameter ingesteld op null.
We slaan de waarde van this
op in een tijdelijke variabele me omdat de waarde van this bij asynchrone eventafhandelaars uit scope geraken en hun waarde kunnen verliezen.
Het antwoord van de server verwerken
Het is tijd om het antwoord van de server effectief te verwerken. De functie moet drie dingen doen:
- Is het antwoord een foutmelding of niet?
- Het formaat voor het antwoord voorbereiden.
- Het antwoord doorgegeven aan de gewenste functie.
We voegen daarvoor de volgende code toe:
this.request.onreadystatechange = function() { // handel de event af als de request volledig uitgevoerd is var response = null; if (me.request.readyState == 4) { // Bereid het antwoord voor in het gewenste formaat switch (me.responseFormat) { case 'text': response = me.request.responseText; break; case 'xml': response = me.request.responseXML; break; case 'object': response = request; break; } // Als er een fout is opgetreden handel die af, in het andere geval // geef het antwoord door aan de functie die het antwoord afhandelt if (me.request.status >= 200 && me.request.status <= 299) { if (!me.responseHandle) { alert('Geen response handler gedefiniëerd voor dit ' + 'XMLHttpRequest object.'); return; } else { me.responseHandle(response); } } else { me.errorHandle(response); } } };
Als het antwoord volledig is teruggestuurd door de server wordt de status eigenschap van het XMLHttpRequest
object ingesteld. Die eigenschap bevat een HTTP status code van de uitgevoerde request. De code kan 404 zijn als de pagina niet gevonden wordt, 500 als de fout optrad in de script aan de serverzijde, 200 als de request met succes is uitgevoerd enz. Een volledige lijst van die codes vind je in de HTTP specificatie (RFC 2616).
Het is haast onmogelijk om al die foutcodes te onthouden. Gelukkig heeft het XMLHttpRequest
object een eigenschap statusText
die een korte beschrijving van de fout bevat.
Onze Ajax
basisklasse kan het antwoord van de server verwerken in drie formaten, als een gewone JavaScript string, als een XML document object toegankelijk via de W3C XML DOM en als een XMLHttpRequest object dat gebruikt werd om de request op te zetten.
De inhoud van het antwoord kan via twee eigenschappen van het XMLHttpRequest
object opgehaald worden:
responseText
: die eigenschap bevat het antwoord van de server als een gewone string. Als er een fout is opgetreden bevat die eigenschap HTML foutpagina van de server. Vanaf er antwoord gekomen is van de server (readyState = 4) bevat die eigenschap gegevens.responseXML
: die eigenschap bevat een XML document. Als het antwoord niet in XML formaat is, is die eigenschap leeg.
Standaard wordt de responseFormat
eigenschap van onze Ajax
klasse ingesteld op text. De response handler retourneert het antwoord van de server als een JavaScript string. Als je met XML werkt moet je eigenschap instellen op xml. De laatste mogelijkheid bestaat erin de eigenschap in te stellen op object waardoor je een XMLHttpRequest
object terugkrijgt. Hetzelfde als je verstuurd hebt.
De foutafhandelaar
Als de status
eigenschap aangeeft dat er een fout is opgetreden tijdens de request (de waarde ervan ligt buiten het interval 200 en 299) wordt het antwoord van de server doorgegeven aan de error handler in de errorHandler
eigenschap. De errorHandler
verwijst naar de volgende functie:
this.errorHandle = function() { var errorWindow; // Toon een foutmelding in een popup window try { errorWindow = window.open('', 'errorWin'); errorWindow.document.body.innerHTML = this.responseText; } // Als pop-ups in de browser geblokkeerd zijn, zeg het aan de gebruiker catch(e) { alert('Er is een fout opgetreden, maar de foutmelding kan niet ' + ' getoond worden omdat je browser pop-ups blokkeert.\n' + 'Je moet pop-ups toelaten voor die Web site als je de foutmeldingen wil zien.'); } }:
De request afbreken
Het gebeurt dat het lang duurt vooraleer een pagina in de browser geladen wordt. De browser heeft een stop knop. Onze Ajax
klasse moet daar ook rekening mee houden en kunnen afbreken. Daarvoor voegen we de volgende methode aan onze klasse toe:
// De request annuleren this.abort = function() { if (this.request) { this.request.onreadystatechange = function() { }; this.request.abort(); this.request = null; } };
Die methode stelt de onreadystatechange
event handler in op een lege functie, roept de abort methode van het XMLHttpRequest
object aan en vernietigt tenslotte het object. Je moet de onreadystatechange
event handler op nul instellen om vele implementaties van het XMLHttpRequest
object die onreadystate
event afvuren als de request wordt afgebroken.
Een GET request versturen
De Ajax klasse heeft nog vier dingen nodig om te kunnen werken:
- een URL;
- een handler functie voor het antwoord;
- de methode accepteert een derde optionele parameter waarmee je het standaard ingestelde formaat kan wijzigen;
- met de vierde parameter kan je een access token meegeven om een geverifiëerde request te maken;
We voegen de getRequest
methode toe:
this.getRequest = function(url, handle, format, accessToken) { this.url = url; this.responseHandle = handle; this.responseFormat = format || 'text'; this.accessToken = accessToken; this.setupRequest(); };
Een POST request versturen
Met de methode postRequest
kan je gegevens in de body van de request naar de server versturen. Daarom heeft deze methode twee extra parameters:
format
: het formaat waarin de gegevens door de server moeten worden teruggestuurd;contentType
: het formaat waarin de gegevens naar de server worden gestuurd; voor Google API call's is dat bijvoorbeeld application/json; zo weet de server van welk soort gegevenstype de gegevens zijn die binnenkomen;
this.postRequest = function(url, postData, handle, format, contentType, accessToken) { this.url = url; this.responseHandle = handle; this.responseFormat = format || 'text'; this.contentType = contentType || 'application/x-www-form-urlencoded' this.accessToken = accessToken; this.method = 'POST'; this.postData = postData; this.setupRequest(); };
Een DELETE request versturen
Een DELETE request is zeer eenvoudig. Als data heeft je alleen een id van het te deleted item mee.
this.deleteRequest = function(url, handle, accessToken) { this.url = url; this.responseHandle = handle; this.accessToken = accessToken; this.method = 'DELETE'; this.setupRequest(); };
Het ajax.js bestand
De volledige code stop je in een bestand met de naam ajax.js:
/* * Original idee by Matthew Eernisse (mde@fleegix.org) * 2007 Joseph Inghelbrecht CVO Deurne, www.cvodeurne.be * 12 juli 2007, zomer in Rièzes, de zon schijnt en de kat slaapt. * 1 november 2016, herfst in Les Charmontois. */ // De Ajax klasseconstructor: function Ajax() { this.request = null; this.url = null; this.status = null; this.statusText = ''; this.method = 'GET'; this.asynchronousFlag = true; this.postData = null; this.readyState = null; this.responseText = null; this.responseXML = null; this.responseHandle = null; this.errorHandle = null; this.responseFormat = 'text'; // 'text', 'xml', 'object' this.mimeType = null; this.accessToken = null; // te gebruiken met oAuth this.contentType = 'application/x-www-form-urlencoded'; // default content-type // Methoden: // Een XMLHttpRequest object maken this.initialize = function() { if (!this.request) { try { // Probeer een object te creëren voor Firefox, Safari, IE7 this.request = new XMLHttpRequest(); } catch (e) { try { // Probeer een object te creëren voor latere versies IE this.request = new ActiveXObject('MSXML2.XMLHTTP'); } catch (e) { try { // Probeer een object te maken met oudere versies van IE this.request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { // Kan geen XMLHttpRequest maken return false; } } } } return true; }; // Een request instellen this.setupRequest = function() { // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het // retourneer if (!this.initialize()) { alert('Het XMLHttpRequest object kan niet gecreëerd worden!'); return; } // Een XMLHttpRequest object is met succes gecreëerd this.request.open(this.method, this.url, this.asynchronousFlag); // Making authenticated request using the request header is accessToken is set if (this.accessToken) { this.request.setRequestHeader('Authorization', 'Bearer ' + this.accessToken); } if (this.method == 'POST') { this.request.setRequestHeader('Content-Type', this.contentType); } var me = this; // fix loss of scope in inner function // Een functie toevoegen om het onreadystatechange event af te handelen this.request.onreadystatechange = function() { // handel de event af als de request volledig uitgevoerd is var response = null; if (me.request.readyState == 4) { // Bereid het antwoord voor in het gewenste formaat switch (me.responseFormat) { case 'text': response = me.request.responseText; break; case 'xml': response = me.request.responseXML; break; case 'object': response = request; break; } // Als er een fout is opgetreden handel die af, in het andere geval // geef het antwoord door aan de functie die het antwoord afhandelt if (me.request.status >= 200 && me.request.status <= 299) { if (!me.responseHandle) { alert('Geen response handler gedefiniëerd voor dit ' + 'XMLHttpRequest object.'); return; } else { me.responseHandle(response); } } else { me.errorHandle(response); } } }; // Verstuur de request met de send method van het XMLHttpRequest object this.request.send(this.postData); }; this.getRequest = function(url, handle, format, accessToken) { this.url = url; this.responseHandle = handle; this.responseFormat = format || 'text'; this.accessToken = accessToken; this.setupRequest(); }; this.postRequest = function(url, postData, handle, format, contentType, accessToken) { this.url = url; this.responseHandle = handle; this.responseFormat = format || 'text'; this.contentType = contentType || 'application/x-www-form-urlencoded' this.accessToken = accessToken; this.method = 'POST'; this.postData = postData; this.setupRequest(); }; this.deleteRequest = function(url, handle, accessToken) { this.url = url; this.responseHandle = handle; this.accessToken = accessToken; this.method = 'DELETE'; this.setupRequest(); }; this.errorHandle = function() { var errorWindow; // Toon een foutmelding in een popup window try { errorWindow = window.open('', 'errorWin'); errorWindow.document.body.innerHTML = this.responseText; } // Als pop-ups in de browser geblokkeerd zijn, zeg het aan de gebruiker catch (e) { // alert('Er is een fout opgetreden, maar de foutmelding kan niet ' + // ' getoond worden omdat je browser pop-ups blokkeert.\n' + // 'Je moet pop-ups toelaten voor die Web site als je de foutmeldingen wil zien.'); var feedback = document.getElementById('feedback'); if (!feedback) { feedback = document.createElement('div'); document.body.appendChild(feedback); } var p = document.createElement('p'); var textContent = document.createTextNode(this.responseText); p.appendChild(textContent); feedback.appendChild(p); } }; // De request annuleren this.abort = function() { if (this.request) { this.request.onreadystatechange = function() {}; this.request.abort(); this.request = null; } }; }